﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using LyncServerExtension.Monitoring;
using System.Management.Automation.Runspaces;
using System.Collections.Concurrent;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;

namespace LyncServerExtension
{
    /// <summary>
    /// Class to regroup Lync server related operations.
    /// </summary>
    public sealed class LyncServerProxy
    {
        private string domainName;
        private string serverName;
        private string sefaUtilPath;
        private IEventLog eventLog;
        private Dictionary<string, LyncUser> userMap;

        /// <summary>
        /// Gets the whole lync users map.
        /// </summary>
        public Dictionary<string, LyncUser> UserMap
        {
            get
            {
                if (userMap == null)
                    GetUsers();
                return userMap;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LyncServerProxy"/> class.
        /// </summary>
        /// <param name="domainName">Name of the domain.</param>
        /// <param name="serverName">Name of the server.</param>
        /// <param name="sefaUtilPath">The SEFAUtil tool path.</param>
        /// <param name="eventLog">The event log.</param>
        public LyncServerProxy(string domainName, string serverName, string sefaUtilPath, IEventLog eventLog)
        {
            if (domainName == null)
                throw new ArgumentNullException("domainName");

            if (serverName == null)
                throw new ArgumentNullException("serverName");

            if (eventLog == null)
                throw new ArgumentNullException("eventLog");

            if (sefaUtilPath == null)
                throw new ArgumentNullException("sefaUtilPath");

            this.domainName = domainName;
            this.serverName = serverName;
            this.sefaUtilPath = string.Concat('"', sefaUtilPath, '"');
            this.eventLog = eventLog;
        }

        /// <summary>
        /// Gets the Lync users from the Active Directory.
        /// AD Filter: (&(objectClass=user)(msRTCSIP-UserEnabled=TRUE))
        /// </summary>
        /// <returns>An array of users.</returns>
        public LyncUser[] GetUsers()
        {
            LyncUser[] users;
            try
            {
                Domain domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, domainName));
                using (var directorySearcher = new DirectorySearcher(domain.GetDirectoryEntry(), USER_FILTER, USER_PROPERTIES))
                {
                    using (var searchResults = directorySearcher.FindAll())
                    {
                        users = new LyncUser[searchResults.Count];
                        int i = 0;

                        foreach (SearchResult result in searchResults)
                        {
                            users[i++] = new LyncUser
                            {
                                AccountName = (Environment.UserDomainName + "\\" + GetUserTextProperty(result, ACCOUNT_NAME_PROPERTY)).ToLower(),
                                Sip = GetUserTextProperty(result, SIP_PROPERTY).Substring(4).ToLower(),
                                FirstName = GetUserTextProperty(result, FIRST_NAME_PROPERTY),
                                LastName = GetUserTextProperty(result, LAST_NAME_PROPERTY),
                                Photo = GetUserProperty(result, PHOTO_PROPERTY) as byte[]
                            };
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new LyncServerExtensionException("Unable to get users.", ex);
            }

            if (userMap == null)
            {
                userMap = new Dictionary<string, LyncUser>();
                foreach (var user in users)
                    userMap.Add(user.Sip, user);
            }
            return users;
        }

        private const string USER_FILTER = "(&(objectClass=user)(msRTCSIP-UserEnabled=TRUE))";
        private const string ACCOUNT_NAME_PROPERTY = "sAMAccountName";
        private const string SIP_PROPERTY = "msRTCSIP-PrimaryUserAddress";
        private const string FIRST_NAME_PROPERTY = "givenName";
        private const string LAST_NAME_PROPERTY = "sn";
        private const string PHOTO_PROPERTY = "thumbnailPhoto";
        private static readonly string[] USER_PROPERTIES = { ACCOUNT_NAME_PROPERTY, SIP_PROPERTY, FIRST_NAME_PROPERTY, LAST_NAME_PROPERTY, PHOTO_PROPERTY };
       
        private object GetUserProperty(SearchResult result, string name)
        {
            ResultPropertyValueCollection rc = result.Properties[name];

            if (rc.Count == 0)
                return null;

            return rc[0];
        }

        private string GetUserTextProperty(SearchResult result, string name)
        {
            object obj = GetUserProperty(result, name);

            if (obj == null)
                return string.Empty;

            return (string)obj;
        }

        public LyncUser[] GetDelegates(string userSip)
        {
            LyncUser[] delegates;
            string args = string.Format("/server:{0} sip:{1}", serverName, userSip);
            string output;

            if (!RunSefaUtilCommand(args, out output))
                throw new LyncServerExtensionException(string.Format("Unable to get user '{0}' delegates.", userSip));

            List<string> ls = new List<string>();
            int start = output.IndexOf("sip:", 0) + 4, end = 0;
            while ((start = output.IndexOf("sip:", start)) > 0)
            {
                start += 4;
                end = output.IndexOf(' ', start);

                if (end < 0)
                    end = output.Length - 2;

                ls.Add(output.Substring(start, end - start));
            }

            delegates = new LyncUser[ls.Count];
            for (int i = 0; i < ls.Count; ++i)
                if (!UserMap.TryGetValue(ls[i], out delegates[i]))
                    throw new LyncServerExtensionException(string.Format("Unknown user '{0}'.", ls[i]));

            return delegates;
        }

        /// <summary>
        /// Sets the call forwarding delegation using the SEFAUtil tool.
        /// </summary>
        /// <param name="param">The call forwarding parameters.</param>
        public void SetCallForwardingDelegation(LyncCallForwardingParameter param)
        {
            LyncUser owner, delegate_;

            if (!UserMap.TryGetValue(param.OwnerSip, out owner))
                throw new LyncServerExtensionException(string.Format("Unknown user '{0}'.", param.OwnerSip));

            if (!UserMap.TryGetValue(param.DelegateSip, out delegate_))
                throw new LyncServerExtensionException(string.Format("Unknown user '{0}'.", param.DelegateSip));

            string args = param.IsEnabled
                ? string.Format("/server:{0} sip:{1} /adddelegate:{2} /fwdtodelegates", serverName, param.OwnerSip, param.DelegateSip)
                : string.Format("/server:{0} sip:{1} /removedelegate:{2}", serverName, param.OwnerSip, param.DelegateSip);
            string output;

            if (!RunSefaUtilCommand(args, out output))
                throw new LyncServerExtensionException(string.Format("Unable to {0} delegation between owner '{1}' and delegate '{2}'.",
                    param.IsEnabled ? "add" : "remove", param.OwnerSip, param.DelegateSip));
        }

        private bool RunSefaUtilCommand(string args, out string output)
        {
            output = null;
            try
            {
                eventLog.Log(EventType.Information, "SEFAUtil [command]: {0} {1}", sefaUtilPath, args);
                eventLog.Log(EventType.Information, "SEFAUtil [identity]: {0}", System.Security.Principal.WindowsIdentity.GetCurrent().Name);

                ProcessStartInfo processInfo = new ProcessStartInfo(sefaUtilPath, args);
                processInfo.UseShellExecute = false;
                processInfo.RedirectStandardOutput = true;
                processInfo.RedirectStandardError = true;

                Process process = new Process();
                process.StartInfo = processInfo;
                process.Start();

                output = process.StandardOutput.ReadToEnd();
                process.WaitForExit();

                eventLog.Log(EventType.Information, "SEFAUtil [output]: {0}", output);
                eventLog.Log(EventType.Information, "SEFAUtil [error]: {0}", process.StandardError.ReadToEnd());

                return (process.ExitCode == 0) && !string.IsNullOrEmpty(output);
            }
            catch (Exception ex)
            {
                eventLog.Log(EventType.Error, "SEFAUtil [exception]: {0}", ex.ToString());
            }
            return false;
        }
    }
}
